package nl.dxn.tunnel.client.handel;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.flush.FlushConsolidationHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.internal.EmptyArrays;
import nl.dxn.tunnel.client.Constants;
import nl.dxn.tunnel.client.config.IpPortProperties;
import nl.dxn.tunnel.client.config.TcpConfigProperties;
import nl.dxn.tunnel.client.executors.AuthExecutor;
import nl.dxn.tunnel.codec.ByteArrayCodec;
import nl.dxn.tunnel.enums.TransferMessageType;
import nl.dxn.tunnel.po.TransferMessage;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 服务器连接处理器
 */
public class ProxyHttpHandler extends SimpleChannelInboundHandler<TransferMessage> {

    private static final Logger logger = LoggerFactory.getLogger(ProxyHttpHandler.class);

    private TcpConfigProperties http;

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        super.channelWritabilityChanged(ctx);
    }

    public ProxyHttpHandler(TcpConfigProperties http) {
        this.http = http;
    }

    @Override
    public void channelRead0(ChannelHandlerContext context, TransferMessage message) throws Exception {
        try {
            if (message.getType() == TransferMessageType.TYPE_KEEPALIVE) {
                return;
            }

            if (message.getType() == TransferMessageType.TYPE_AUTH) {
                AuthExecutor.execute(context, message);
                return;
            }

            if (message.getType() == TransferMessageType.TYPE_CONNECTED) {
                dealConnection(context,message);
                return;
            }

            executeHttp(context, message);
        } catch (Exception ex) {
            logger.error("", ex);
        }
    }

    /**
     * 断开连接
     *
     * @param ctx
     * @throws Exception
     */

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("http隧道客户端-服务端代理连接中断");
        AuthExecutor.executeAgentDisConnect(ctx);
        logger.info("http隧道客户端-服务端代理连接，后事处理完毕");
    }

    /**
     * 连接异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("",cause);
        ctx.close();
    }

    public void dealConnection(ChannelHandlerContext context, TransferMessage message)
    {
        message.getConfig().setResult(null);
        logger.info("http隧道客户端-转发http请求：{} {}", message.getConfig().getServerId(), message.getConfig().getChannelId());
        ConcurrentHashMap<String, Pair<EventLoopGroup, Channel>> hashMap = context.channel().attr(Constants.LOCAL).get();
        if (hashMap == null) {
            hashMap = new ConcurrentHashMap<>();
            context.channel().attr(Constants.LOCAL).set(hashMap);
        }

        Pair<String, Integer> ipPair = getIpPort(message);
        String transferIp = ipPair.getKey();
        int transferPort = ipPair.getRight();

        if (StringUtils.isBlank(transferIp)
                || transferPort <= 0) {
            message.getConfig().setResult("隧道客户端未配置代理的远程服务地址");
            message.setData(EmptyArrays.EMPTY_BYTES);
            context.writeAndFlush(message);
            return;
        }

        EventLoopGroup localGroup = new NioEventLoopGroup();
        Bootstrap localBootstrap = new Bootstrap();
        localBootstrap.group(localGroup).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, false)
                .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel channel) throws Exception {
                channel.pipeline().addLast(new ByteArrayCodec());
                channel.pipeline().addLast(new ChunkedWriteHandler());
                channel.pipeline().addLast(new FlushConsolidationHandler(5));
                channel.pipeline().addLast(new HttpClientHandel(message.getConfig()));
            }
        });
        localBootstrap.connect(transferIp, transferPort).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                future.channel().attr(Constants.PROXY).set(context.channel());
                context.channel().attr(Constants.LOCAL).get().put(message.getConfig().getChannelId(), Pair.of(localGroup, future.channel()));

                message.setType(TransferMessageType.TYPE_CONNECTED);
                message.setData(EmptyArrays.EMPTY_BYTES);
                context.writeAndFlush(message);
            } else {
                message.getConfig().setResult("远程客户端服务连接失败");
                message.setData(EmptyArrays.EMPTY_BYTES);
                context.writeAndFlush(message);
                localGroup.shutdownGracefully();
            }
        });
    }

    public void executeHttp(ChannelHandlerContext context, TransferMessage message) {
        try {
            logger.info("http转发 {}", message.getConfig().getServerId());
            ConcurrentHashMap<String, Pair<EventLoopGroup, Channel>> hashMap = context.channel().attr(Constants.LOCAL).get();
            Pair<EventLoopGroup, Channel> pair = hashMap.get(message.getConfig().getChannelId());
            if (pair != null) {
                pair.getRight().writeAndFlush(message.getData());
            }
            else
            {
                logger.error("未获取到远程客户端服务连接");
                message.getConfig().setResult("未获取到远程客户端服务连接");
                message.setData(EmptyArrays.EMPTY_BYTES);
                context.writeAndFlush(message);
            }
        }
        catch (Exception ex)
        {
            logger.error("", ex);
            message.getConfig().setResult("远程客户端服务数据发送失败");
            message.setData(EmptyArrays.EMPTY_BYTES);
            context.writeAndFlush(message);
        }
    }

    private Pair<String, Integer> getIpPort(TransferMessage message) {
        String serverId = message.getConfig().getServerId();
        if (StringUtils.isEmpty(serverId)) {
            return Pair.of(http.getDefaultIp(), http.getDefaultPort());
        }

        if(http.getServerTransferMap() == null || http.getServerTransferMap().size()==0)
        {
            return Pair.of(http.getDefaultIp(), http.getDefaultPort());
        }

        IpPortProperties ipPortProperties = http.getServerTransferMap().get(serverId);
        if (ipPortProperties == null
                || StringUtils.isBlank(ipPortProperties.getIp())
                || ipPortProperties.getPort() <= 0) {
            return Pair.of(http.getDefaultIp(), http.getDefaultPort());
        }

        return Pair.of(ipPortProperties.getIp(), ipPortProperties.getPort());
    }
}
